Kubernetes 部署 HTTP 服务
编写一个简单的服务
生成一些笑话
package main
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"time"
)
type Joke struct {
Setup string `json:"setup"`
Punchline string `json:"punchline"`
}
func main() {
rand.Seed(time.Now().UnixNano())
jokes := []Joke{
{Setup: "为什么老虎不喜欢玩扑克牌?", Punchline: "因为他们总是被抓牌!"},
{Setup: "为什么小学生喜欢上学?", Punchline: "因为他们离开了家庭作业!"},
{Setup: "为什么会有“狗不理包子”?", Punchline: "因为狗不会做包子啊!"},
{Setup: "一个数学家和一只普通人有什么不同?", Punchline: "数学家有解!"},
{Setup: "为什么小鸟飞不高?", Punchline: "因为它的体重不够大!"},
}
http.HandleFunc("/joke", func(w http.ResponseWriter, r *http.Request) {
joke := jokes[rand.Intn(len(jokes))]
jsonData, err := json.Marshal(joke)
if err != nil {
http.Error(w, "Unable to marshal JSON", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(jsonData)
})
fmt.Println("Starting server on port 8080...")
http.ListenAndServe(":8080", nil)
}
访问上面的服务
curl http://localhost:8080/joke
打包成容器镜像
创建一个 Dockerfile 文件,指定容器的基础镜像、应用程序代码的拷贝方式、容器启动命令等信息。例如:
# 基础镜像
FROM golang:1.17
# 设置工作目录
WORKDIR /app
# 拷贝应用程序代码
COPY . .
# 构建应用程序
RUN go build -o jokes main.go
# 启动命令
CMD ["./jokes"]
执行 docker build 命令来构建镜像,例如:
# 这里使用私有库
docker build -t localhost:5000/jokes:latest .
# 推送到私有库
docker push localhost:5000/jokes:latest
使用以下命令查看本地 Docker 镜像仓库中的镜像:
curl -s http://localhost:5000/v2/_catalog | jq .
执行上述命令后,将会得到一个 JSON 格式的输出,其中 repositories 键对应的值即为私有仓库中包含的镜像列表。如果私有仓库为空,则该值将为空数组。可以进一步执行如下命令,查看某个镜像的标签列表:
curl -s http://localhost:5000/v2/<IMAGE_NAME>/tags/list | jq .
其中,<IMAGE_NAME>
需要替换为你需要查询标签的镜像名称。执行上述命令后,将会得到一个 JSON 格式的输出,其中 tags 键对应的值即为该镜像对应的标签列表。
部署到 Kubernetes 里面
在项目根目录下创建两个 YAML 配置文件(这里使用私有的 Docker 仓库):
deployment.yaml
文件内容如下:
apiVersion: apps/v1
kind: Deployment # 定义了 Kubernetes 部署的类型
metadata:
name: jokes-deployment
labels:
app: jokes
spec:
replicas: 1 # 定义了要启动的 Pod 的数量
selector: # 定义了要选择哪些 Pod 进行部署
matchLabels:
app: jokes
template: # 定义了要部署的 Pod 的模板
metadata:
labels:
app: jokes
spec:
containers: # 定义了要运行的容器
- name: jokes
image: localhost:5000/jokes:latest
ports:
- containerPort: 8080
service.yaml
文件内容如下:
apiVersion: v1
kind: Service
metadata:
name: jokes-service
labels:
app: jokes
spec:
selector:
app: jokes
ports:
- name: http
port: 21980 # 定义了服务暴露的端口
targetPort: 8080
type: LoadBalancer
在 Kubernetes 中,Service 的 port 和 targetPort 是用于指定 Service 监听的端口和后端 Pod 上暴露的端口,它们的区别如下:
port
:Service 暴露的端口,即客户端可以使用的端口。当客户端通过 Service 访问后端 Pod 时,需要使用的端口就是 port 指定的端口。如果未指定 port,则默认使用 Service 的名称作为端口号。targetPort
:后端 Pod 暴露的端口,即 Service 负载均衡流量时使用的端口。如果 Service 负载均衡到的后端 Pod 上没有暴露 targetPort 指定的端口,则请求将无法正确路由到后端 Pod 上。
举个例子,假设您有一个后端 Pod,该 Pod 在容器内部运行一个 Web 服务器,监听端口 8080。您还创建了一个 Service,该 Service 将请求路由到该 Pod 上。
为了让 Service 能够正确地路由请求,您需要将 Service 的 targetPort 设置为 8080,以指定应将请求路由到 Pod 上的 8080 端口。而客户端需要使用的端口可以设置为任何您希望的端口,例如 80、443 等,这些都可以在 Service 的 port 属性中指定。
需要注意的是,当您使用 NodePort 或 LoadBalancer 类型的 Service 时,还可以使用 nodePort 属性将 Service 公开到主机上的端口。nodePort 是 NodePort 类型 Service 使用的端口,它将流量路由到 Service 的 port 上,并且这个 nodePort 端口将被公开到集群节点的外部。在这种情况下,nodePort 和 port 是不同的端口。(不指定则随机生成)
使用以下命令部署应用程序:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
这将会启动一个包含一个副本的 Deployment 和一个 LoadBalancer 类型
使用以下命令来查看部署的状态:
kubectl get deployments
kubectl get services
后面的这个 30462 是 Kubernetes Service 的节点端口(node port)。当创建一个 Kubernetes Service 时,它将创建一个虚拟 IP 地址,该 IP 地址将指向集群中运行的所有 Pod 的一个或多个实例。为了访问该服务,需要公开此虚拟 IP 地址。
Kubernetes Service 可以通过多种方式公开,其中一种方式是将其公开为节点端口。节点端口是在 Kubernetes 集群中的每个节点上公开服务的端口号。
在创建 Kubernetes Service 时,可以选择将服务公开为节点端口。在上面的示例中,服务名称为 jokes-service,类型为 LoadBalancer,并且在节点端口 30462 上公开端口 21980,这意味着在节点上访问 jokes-service 服务的端口 30462 时,将使用负载平衡器将流量路由到服务的端口 21980。
可以直接进入启动的服务的容器,去检查一下服务是否启动了
# kubectl exec -it <pod-name> -- <command>
kubectl exec -it jokes-deployment-67b489cfc5-h444z -- /bin/bash
根据上面暴露的 IP 去访问服务(暴露的其实就是本地 IP,所以这里直接使用 localhost),可以看到服务的返回值了
检查服务状态
如果 LoadBalancer 类型的 Service 的 EXTERNAL-IP 列显示为 <pending>
,说明该 Service 暂时没有分配到外部 IP。
要查看 LoadBalancer 的分配状态,可以执行以下命令:
kubectl describe services <service-name>
该命令会显示 Service 的详细信息,包括当前的状态、事件等。
如果没有啥异常,可以检查一下是不是 Port 被占用了,修改后就可以访问了
k8s 辨析 port、NodePort、targetPort、containerPort 区别
NodePort
nodePort 提供了集群外部客户端访问 Service 的一种方式,nodePort 提供了集群外部客户端访问 Service 的端口,通过 nodeIP:nodePort 提供了外部流量访问 k8s 集群中 service 的入口。
比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应 service 的 type=NodePort,nodePort=30001。其他用户就可以通过浏览器 http://node:30001 访问到该web服务。
而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。
而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置 service 的 NodePort。
port
port 是暴露在 clusterIP 上的端口,port 提供了集群内部客户端访问 service 的入口,即 clusterIP:port
。
mysql 容器暴露了 3306 端口(参考 DockerFile),集群内其他容器通过 33306 端口访问 mysql 服务,但是外部流量不能访问 mysql 服务,因为 mysql 服务没有配置 NodePort。对应的 service.yaml 如下:
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- port: 33306
targetPort: 3306
selector:
name: mysql-pod
targetPort
targetPort 是 pod 上的端口,从 port/nodePort 上来的数据,经过 kube-proxy 流入到后端 pod 的 targetPort 上,最后进入容器。
与制作容器时暴露的端口一致(使用 DockerFile 中的 EXPOSE),例如官方的 nginx(参考DockerFile)暴露80端口。 对应的 service.yaml 如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort # 配置NodePort,外部流量可访问k8s中的服务
ports:
- port: 30080 # 服务访问端口,集群内部访问的端口
targetPort: 80 # pod控制器中定义的端口(应用访问的端口)
nodePort: 30001 # NodePort,外部客户端访问的端口
selector:
name: nginx-pod
containerPort
containerPort是在pod控制器中定义的、pod中的容器需要暴露的端口。
例如,mysql 服务需要暴露 3306 端口,redis 暴露 6379 端口
apiVersion: v1
kind: ReplicationController
metadata:
name: redis-master
labels:
name: redis-master
spec:
replicas: 1
selector:
name: redis-master
template:
metadata:
labels:
name: redis-master
spec:
containers:
- name: master
image: kubeguide/redis-master
ports:
- containerPort: 6379 # 此处定义暴露的端口
检查服务日志
如果上面的服务一直没有启来,可以通过以下方式去检查为啥服务没有起来
1、使用 kubectl describe deployment
命令查看 Deployment 的详细信息,包括 Pod 的创建状态、事件和各个容器的状态:
kubectl describe deployment <deployment-name>
2、使用 kubectl get pods
命令查看 Pod 的状态,包括 Pod 是否已经创建和正在运行的容器:
kubectl get pods
如果 Pod 处于 Pending 状态,可能是因为调度器无法找到合适的节点来运行 Pod。可以使用 kubectl describe pod 命令查看 Pod 的详细信息,包括 Pod 调度的状态和调度器的事件:
kubectl describe pod <pod-name>
3、使用 kubectl logs
命令查看容器的日志,以查找容器启动失败的原因:
kubectl logs <pod-name> <container-name>
如果容器未能启动,可能是由于容器映像中缺少必要的文件或配置,或者容器启动时发生错误。在容器日志中查找错误消息和异常堆栈跟踪,可以帮助你确定容器启动失败的原因。
4、最后,可以查看 Kubernetes 控制面板的事件日志,以查找与 Deployment 相关的事件和错误:
kubectl get events
这些事件可能包括 Pod 调度失败、容器启动失败等等。通过查看事件日志,可以快速定位 Deployment 未启动的原因。
更新服务
在 Kubernetes 中,更新 Deployment 的常见做法是通过执行 kubectl apply 命令来部署更新。这个命令会自动检测当前的状态,并尝试更新它。如果 Deployment 对象不存在,它会被创建;如果存在,那么 Deployment 的更新策略会根据更新时的参数进行更新,比如滚动更新等。
如果已经修改了 Deployment 的配置,可以通过以下命令重新加载:
kubectl apply -f deployment.yaml
这会将 deployment.yaml
文件中的配置应用到当前的 Deployment 对象中,并尝试更新它。如果存在任何问题,kubectl apply 命令将输出错误信息。否则,Deployment 将开始滚动更新,直到新的 Pod 代替旧的 Pod。